#include "Quectel_EC200.h"
#include <SHA256.h>
#include <arduino_base64.hpp>
#include <TimeLib.h>
#include <time.h>
#include <ArduinoJson.h>
#include <EEPROM.h>
#include <DMD32.h>
#include "FS.h"
#include "SPIFFS.h"
#include <stdlib.h>

#define DISPLAYS_ACROSS 2
#define DISPLAYS_DOWN 1

#define MQTT_DOMAIN_FORMAT "%s.iotcloud.tencentdevices.com"
#define SHA256_HMAC_SIZE 32
#define MQTT_TOPIC_CONTROL "%s/%s/control"
#define MQTT_TOPIC_DATA "%s/%s/data"

#define HTTP_HOST "iot-api.randqun.com"
#define HTTP_PATH "/app/device/dynamicRegister"

#define USART_RX_4G 26
#define USART_TX_4G 27
#define SENSOR_READ 34
#define AUDIO_PLAYING_READ 25
#define AUDIO_PATH "/audio"
#define LED_HEX_PATH "/led"

#define MAX_DOWN_CNT 500



#define MAX_DOWN_CNT 500


TaskHandle_t ledTaskHandle;
TaskHandle_t warningTaskHandle;
int warningValue;
bool isAudioPlaying;
EEPROMClass DEVICE_SECRET("eeprom0", 0x128);
DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);
String DYNAMIC_REGISTER_SALT = "V1GCdyNxzg5bUraW";
const char *productId = "05HSZVNCPI";
hw_timer_t *timer = NULL;
char deviceName[6];
const char *deviceSecret;
int strLen = 8;
char mqttDomain[128];
char mqttClientId[128];
char mqttUsername[128];
char mqttPwd[256];
long lastCheck;
int ledTaskState;



char controlTopic[50];
char dataTopic[50];
String single;
unsigned char ss;
char audioStr[512];
unsigned char audioPackage[256];
uint8_t ledHex[50][32] = {
  { 0, 0, 32, 0, 32, 127, 32, 65, 32, 64, 47, 94, 41, 82, 41, 82, 105, 82, 169, 82, 41, 82, 47, 94, 32, 64, 32, 64, 32, 127, 32, 0 },
  { 0, 0, 64, 2, 65, 2, 65, 26, 65, 34, 65, 2, 65, 2, 95, 254, 65, 2, 65, 2, 65, 2, 65, 2, 64, 2, 127, 248, 0, 6, 0, 1 },
  { 0, 0, 0, 0, 4, 14, 4, 2, 53, 226, 45, 18, 37, 34, 37, 2, 37, 2, 37, 252, 228, 0, 36, 0, 23, 240, 8, 12, 4, 2, 0, 1 },
  { 0, 0, 2, 2, 2, 2, 4, 226, 10, 26, 50, 6, 194, 114, 50, 130, 10, 26, 4, 34, 2, 2, 97, 224, 90, 16, 68, 32, 127, 255, 0, 0 },
  { 0, 0, 2, 16, 34, 17, 36, 146, 40, 148, 254, 144, 40, 144, 36, 144, 0, 158, 36, 145, 40, 146, 254, 144, 40, 148, 36, 146, 34, 17, 2, 16 },
  { 0, 0, 0, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 255, 254, 0, 2, 0, 2, 0, 2, 15, 254, 0, 2, 0, 2, 0, 2 },
  { 0, 0, 0, 0, 31, 252, 16, 2, 16, 1, 16, 2, 208, 96, 48, 128, 9, 0, 4, 0, 36, 136, 36, 132, 231, 254, 52, 128, 8, 128, 4, 128 },
  { 0, 0, 0, 2, 0, 2, 7, 242, 4, 146, 52, 146, 44, 146, 36, 146, 39, 242, 36, 146, 228, 146, 36, 146, 20, 146, 15, 242, 4, 2, 2, 2 }
};



void IRAM_ATTR triggerScan() {
  dmd.scanDisplayBySPI();
}

void getTime(){
  String t = EC200module.getModelTime();  //24/04/02,16:10:34+32
  Serial.println(t);
  int year = atoi(t.substring(0, 2).c_str()) + 2000;


  int month = atoi(t.substring(3, 5).c_str());

  int day = atoi(t.substring(6, 8).c_str());

  int hour = atoi(t.substring(9, 11).c_str());

  int min = atoi(t.substring(12, 14).c_str());
  int sec = atoi(t.substring(15, 17).c_str());

  setTime(hour, min, sec, day, month, year);
}



void setup() {

  delay(1000);
  Serial.begin(115200);
  Serial2.begin(115200);

  pinMode(SENSOR_READ, INPUT);
  pinMode(AUDIO_PLAYING_READ, INPUT);
  SPIFFS.begin(true);


  getLedHex();
  dmdSetup();
  xTaskCreate(
 showLED,          //指定任务函数，也就是上面那个task1函数
    "showLED",        //任务名称
    10000,            //任务堆栈大小
    NULL,             //作为任务输入传递的参数
    1,                //优先级
    &ledTaskHandle);  //任务句柄

  ledTaskState = 1;
  xTaskCreate(
    warningLoop,          //指定任务函数，也就是上面那个task1函数
    "warningLoop",        //任务名称
    10000,                //任务堆栈大小
    NULL,                 //作为任务输入传递的参数
    1,                    //优先级
    &warningTaskHandle);  //任务句柄


  getChipId();

  EC200module.setup(115200, USART_RX_4G, USART_TX_4G);
  delay(1000);

  getSecret();
  setCallback();
  connect();
  subscribe();
  delay(1000);
}

void loop() {
  EC200module.mqttLoop();
}

void warningLoop(void *parameter) {
  for (;;) {

    warningValue = digitalRead(SENSOR_READ);
    isAudioPlaying = !digitalRead(AUDIO_PLAYING_READ);
    vTaskDelay(100); // 这里不delay就会看门狗重启
    //    Serial.println("warningValue");
    //  Serial.println(warningValue);
    // 有人靠近的时候

    if (warningValue > 0) {
      if (!isAudioPlaying) {
        vTaskDelay(3000);
        playAudio();
        vTaskDelay(500);
        Serial.println("play");
      }

      if (ledTaskState == 0) {
        vTaskResume(ledTaskHandle);
        ledTaskState = 1;
      }

    } else {
      if (ledTaskState == 1) {

        vTaskSuspend(ledTaskHandle);
        dmd.clearScreen(true);
        ledTaskState = 0;
      }
    }
  }
}

String hmac256(const String &signcontent, const String &ds) {
  byte hashCode[SHA256_HMAC_SIZE];
  SHA256 sha256;

  const char *key = ds.c_str();
  size_t keySize = ds.length();

  sha256.resetHMAC(key, keySize);
  sha256.update((const byte *)signcontent.c_str(), signcontent.length());
  sha256.finalizeHMAC(key, keySize, hashCode, sizeof(hashCode));
  String sign = "";
  for (byte i = 0; i < SHA256_HMAC_SIZE; ++i) {
    sign += "0123456789ABCDEF"[hashCode[i] >> 4];
    sign += "0123456789ABCDEF"[hashCode[i] & 0xf];
  }
  return sign;
}



bool connect() {

  int inputStringLength = sizeof(deviceSecret);
  int decodeLen = base64::decodeLength(deviceSecret);
  char rawKey[decodeLen];
  base64::decode(deviceSecret, (uint8_t *)rawKey);
  rawKey[decodeLen] = '\0';
  int connid = random(10000, 99999);
  struct tm timeinfo;
  long expire = now() + 86400;
  sprintf(mqttClientId, "%s%s", productId, deviceName);
  sprintf(mqttDomain, MQTT_DOMAIN_FORMAT, productId);
  sprintf(mqttUsername, "%s;12010126;%s;%ld", mqttClientId, "0b814", 1720195200);



  // 5. 根据物联网通信平台规则生成 password 字段
  String pwd = hmac256(mqttUsername, rawKey) + ";hmacsha256";
  Serial.println(pwd);
  strcpy(mqttPwd, pwd.c_str());

  
  return EC200module.connectMqtt(mqttDomain, mqttClientId, mqttUsername, mqttPwd);
}

void setCallback() {
  EC200module.setMqttCallback(callback);
}


void callback(char *topic, uint8_t *payload, unsigned int length) {


  vTaskSuspend(ledTaskHandle);
  timerAlarmDisable(timer);

  StaticJsonDocument<200> doc;
  if (strstr(topic, dataTopic)) {
   
    DeserializationError error = deserializeJson(doc, payload);
    if (!error) {
      const char *message = doc["Message"];
      StaticJsonDocument<200> jsonData;
      const char *data = doc["data"];
      
      DeserializationError error = deserializeJson(jsonData, data);
     
      if (strstr(message, "WARNING_UPDATE")) {

        const char *content = jsonData["content"];
        File ledFile = SPIFFS.open(LED_HEX_PATH, FILE_WRITE);


        // uint8_t  contentHZK16  = jsonData["contentHZK16"][100][20];
        strLen = strlen(content) / 3;
       
        for (uint8_t i = 0; i < strLen; i++) {
          for (uint8_t j = 0; j < 32; j++) {
            ledHex[i][j] = jsonData["contentHZK16"][i * 32 + j];
            ledFile.write(jsonData["contentHZK16"][i * 32 + j]);
          }
        }
        ledFile.close();

        String audioHex = jsonData["audioHex"];
        Serial.println(audioHex);

        downloadAudio(audioHex);
        Serial.println("update111");
        updateAudio();
        timerAlarmEnable(timer);
        vTaskResume(ledTaskHandle);
      }
    }
  }
}

void updateScreen() {}



void subscribe() {

  sprintf(controlTopic, MQTT_TOPIC_CONTROL, productId, deviceName);
  sprintf(dataTopic, MQTT_TOPIC_DATA, productId, deviceName);
  EC200module.subscribe(controlTopic);
  EC200module.subscribe(dataTopic);
}

void dynamicRegister() {

  delay(1000);


  char jsonBody[100];



  sprintf(jsonBody, "{\"productId\":\"%s\",\"deviceName\":\"%s\"}", productId, deviceName);



  long timestamp = now();
  char tsStr[10];
  itoa(timestamp, tsStr, 10);



  String str = tsStr + DYNAMIC_REGISTER_SALT;

  MD5Builder md5;
  md5.begin();
  md5.add(str.c_str());
  md5.calculate();

  char postCmd[500];
  char tmpStr[100];
  sprintf(postCmd, "POST %s HTTP/1.1\r\nHost: %s\r\n", HTTP_PATH, HTTP_HOST);

  strcat(postCmd, "Content-Type: application/json\r\n");



  sprintf(tmpStr, "Content-Length: %d\r\n", strlen(jsonBody));
  strcat(postCmd, tmpStr);

  sprintf(tmpStr, "timestamp: %d\r\n", timestamp);
  strcat(postCmd, tmpStr);

  sprintf(tmpStr, "signature: %s\r\n", md5.toString().c_str());
  strcat(postCmd, tmpStr);

  strcat(postCmd, "\r\n");

  strcat(postCmd, jsonBody);





  EC200module.command("AT+QHTTPSTOP", "OK");


  char url[100];
  sprintf(url, "http://%s%s", HTTP_HOST, HTTP_PATH);
  EC200module.command("AT+QHTTPCFG=\"contextid\",1", "OK");
  EC200module.command("AT+QHTTPCFG=\"requestheader\",1", "OK");



  sprintf(tmpStr, "AT+QHTTPURL=%d,10", strlen(url));
  EC200module.command(tmpStr, "CONNECT");


  EC200module.command(url, "OK");

  sprintf(tmpStr, "AT+QHTTPPOST=%d,10,10\r\n", strlen(postCmd));

  EC200module.command(tmpStr, "CONNECT");

  EC200module.command(postCmd, "OK");

  String response = EC200module.command("AT+QHTTPREAD=80");
  Serial.println("....");
  Serial.println(response);
  Serial.println("....");


  int jsonStart = response.indexOf("{");
  int jsonEnd = response.lastIndexOf("}");
  if (jsonStart != -1 && jsonEnd != -1) {
    String json = response.substring(jsonStart, jsonEnd + 1);
    StaticJsonDocument<200> responseBuffer;
    DeserializationError error = deserializeJson(responseBuffer, json);
    uint8_t status = responseBuffer["status"];
    if (status == 1) {
      deviceSecret = responseBuffer["data"]["DevicePsk"];
      Serial.println(deviceSecret);
      DEVICE_SECRET.writeString(0, deviceSecret);
      DEVICE_SECRET.commit();
    }
  }
}

void dmdSetup() {

  // return the clock speed of the CPU
  uint8_t cpuClock = ESP.getCpuFreqMHz();

  // devide cpu clock speed on its speed value by MHz to get 1us for each signal  of the timer
  timer = timerBegin(0, cpuClock, true);
  // Attach triggerScan function to our timer
  timerAttachInterrupt(timer, &triggerScan, true);
  // Set alarm to call triggerScan function
  // Repeat the alarm (third parameter)
  timerAlarmWrite(timer, 300, true);
  // Start an alarm
  timerAlarmEnable(timer);

  //clear/init the DMD pixels held in RAM
  dmd.clearScreen(true);  //true is normal (all pixels off), false is negative (all pixels on)
}


void getChipId() {
  uint32_t chipId = 0;
  for (int i = 0; i < 17; i = i + 8) {
    chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
  }
  sprintf(deviceName, "%02X", chipId);
}

void getSecret() {
  DEVICE_SECRET.begin(DEVICE_SECRET.length());
  String data = DEVICE_SECRET.readString(0);
  if (data.isEmpty()) {
    dynamicRegister();
  } else {
    deviceSecret = data.c_str();
  }
}

void getLedHex() {
  Serial.println("getLedHex");
  File file = SPIFFS.open(LED_HEX_PATH);
  // 文件不存在就直接使用默认的危险区域禁止钓鱼
  if (!file) {
    Serial.println("failed to open file");
    return;
  }
  int i = 0;
  while (file.available()) {
    int j = floor(i / 32);
    int k = i % 32;
    strLen = j + 1;
    ledHex[j][k] = file.read();
    i++;
  }
}

void drawData(uint16_t data, uint8_t offset_x, uint8_t offset_y) {

  for (uint8_t i = 0; i < 16; i++) {
    //  Serial.print((data & (0x8000 >> i)) >0 );
    //0x8000  1000 0000 0000 0000 每次右移和数据相& 然后再看是否大于0，得出每次从高到底的数据
    //  dmd.writePixel(i,offset,GRAPHICS_NORMAL,(data & (0x8000 >> i)) >0 );
    dmd.writePixel(i + offset_x, offset_y, GRAPHICS_NORMAL, (data & (0x8000 >> i)) > 0);
    Serial.print((data & (0x8000 >> i)) > 0 ? "*" : " ");
  }
  Serial.println();
}


void showLED(void *parameter) {

  for (;;) {
    static uint8_t page = 1;
    dmd.clearScreen(true);
    for (uint8_t i = 0; i < 4; i++) {
      uint8_t index = (page - 1) * 4 + i;
      if (strLen > index) {
        // Serial.printf("size:%d,index:%d\r\n", strLen, index);
        drawHZ(ledHex[index], i);
      }
    }
    if (strLen > page * 4) {
      page++;
    } else {
      page = 1;
    }
    vTaskDelay(5000);
  }
}


void drawHZ(uint8_t hz[], uint8_t offset_x) {
  for (uint8_t i = 0; i < 16; i++) {
    drawData(hz[i * 2] << 8 | hz[i * 2 + 1], offset_x * 16, i);
  }

  // Serial.println("");
}



void updateAudio() {

  File file = SPIFFS.open(AUDIO_PATH);
  float fLen = file.size();
  Serial.println("开始更新");

  Serial.println(fLen);

  int package = ceil(fLen / 512);

  unsigned char high = floor(package / 256);
  unsigned char low = package % 256;
  Serial.printf("high:%d", high);
  Serial.printf("low:%d", low);
  unsigned char beginCmd[8] = { 0x7e, 0xA0, 0x00, 0x03, 0x01, high, low, 0xEF };
  Serial2.write(beginCmd, 8);

  delay(100);
  int rest;
  int currentPackage;
  for (int i = 0; i < package; i++) {
    rest = fLen - i * 256;
    currentPackage = std::min(rest, 512);
    audioStr[currentPackage] = '\0';
    file.readBytes(audioStr, currentPackage);
    str2hex(audioPackage, audioStr);
    Serial.printf("进度：%d", i + 1);
    Serial.println();
    // sprintf(process, "进度%d/%d", i +1, package);
    // Serial.println(process);
    Serial2.write(0x7E);
    Serial2.write(0xA1);
    high = floor((i + 1) / 256);
    low = (i + 1) % 256;
    Serial.printf("high:%d", high);
    Serial.printf("low:%d", low);
    Serial2.write(high);
    Serial2.write(low);
    for (int j = 0; j < 256; j++) {
      Serial2.write(audioPackage[j]);
      delay(1);
    }

    Serial2.write(0xef);
  }
  Serial.println("222");
  unsigned char endCmd[7] = { 0x7e, 0xA3, 0x00, 0x02, 0x00, 0x00, 0xEF };
  Serial2.write(endCmd, 7);
  file.close();
  delay(1000);
}

void playAudio() {
  unsigned char playCmd[7] = { 0x7e, 0xA6, 0x00, 0x02, 0x00, 0x01, 0xEF };
  Serial2.write(playCmd, 7);
}


void downloadAudio(String URL) {

  // 电信 ctnet 联通UNINET 移动 cmiot

  Serial.println(0);
  SPIFFS.remove(AUDIO_PATH);
  Serial.println(1);
  File handler = SPIFFS.open(AUDIO_PATH, FILE_WRITE);
  Serial.println(2);
  int start = 0;
  int status;
  String response;
  int downloadCnt = 0;



  do {
    downloadCnt++;
    EC200module.command("AT+QHTTPSTOP", "OK");
    EC200module.command("AT+QHTTPCFG=\"contextid\",1", "OK");
    int end = start + DOWNLOAD_CHUNK_SIZE;
    char range[50];
    sprintf(range, "bytes=%d-%d", start, end);

    EC200module.addHeader("range", range);
    Serial.println(URL);
    Serial.println(URL.length());

    status = EC200module.GetHTTP(URL);
    delay(100);
    Serial.println("status");
    Serial.println(status);
    if (status == 206) {

      response = EC200module.HTTPread();
      handler.print(response);
      start = end + 1;
    }
    delay(1000);
    // 防止网络文件错误然后一直在这下载
  } while ((status == 404 || response.length() >= DOWNLOAD_CHUNK_SIZE + 1) && downloadCnt < MAX_DOWN_CNT);
  handler.close();
}

void str2hex(unsigned char *package, char hex[]) {

  for (int i = 0; i < 256; i++) {
    if (i < strlen(hex)) {
      single = hex[i * 2];
      single += hex[i * 2 + 1];
      ss = strtol(single.c_str(), NULL, 16);

    } else {
      ss = 0x00;
    }
    package[i] = ss;
  }
}
